package evocarcassonne.core.board;

import evocarcassonne.core.board.locations.Position;
import evocarcassonne.core.board.locations.Zone;
import evocarcassonne.core.elements.City;
import evocarcassonne.core.elements.Element;
import evocarcassonne.core.elements.Field;
import evocarcassonne.core.elements.Road;
import evocarcassonne.core.utils.Card;
import evocarcassonne.core.utils.ZoneType;

import java.util.*;

public class Board {

    // positionned tiles
    private Map<Position, Tile> tiles = new HashMap();
    // holes of the board
    private List<Position> holes = new ArrayList();
    //list of cities
    private Set<City> cities = new HashSet();
    // list of fields
    private Set<Field> fields = new HashSet();
    //list of roads
    private Set<Road> roads = new HashSet();
    // use to know already merged tiles

    // Uncompleted cities, roads and fields
    // TODO
    public List<Position> getHoles() {
        return holes;
    }

    public Map<Position, Tile> getTiles() {
        return tiles;
    }

    private void updateHoles(Position pos) {

        Position pN = new Position(pos.x - 1, pos.y);
        Position pS = new Position(pos.x + 1, pos.y);
        Position pE = new Position(pos.x, pos.y + 1);
        Position pW = new Position(pos.x, pos.y - 1);

        if (tiles.get(pN) == null && !holes.contains(pN)) {
            holes.add(pN);
        }

        if (tiles.get(pS) == null && !holes.contains(pS)) {
            holes.add(pS);
        }

        if (tiles.get(pE) == null && !holes.contains(pE)) {
            holes.add(pE);
        }

        if (tiles.get(pW) == null && !holes.contains(pW)) {
            holes.add(pW);
        }
    }

    public void placeTile(Position pos, Tile tile) {

        if (tiles.get(pos) == null) {

            tiles.put(pos, tile);
            holes.remove(pos);

            //update tile position
            tile.setPosition(pos);

            // update holes
            updateHoles(tile.getPosition());

            //update elements
            updateBoardElements(tile);

//            System.out.print("Apres le placement de la tuile " + tile.number + " (" + tile.getPosition().x + "," + tile.getPosition().y + ")");
//            System.out.println(" Il y a :");

            cities.clear();
            fields.clear();
            roads.clear();

            for (Map.Entry<Position, Tile> entry : tiles.entrySet()) {
                Tile tile1 = entry.getValue();

                Set<Element> tileElement = tile1.getElements();
                for (Element element : tileElement) {

                    if (element instanceof City) {
                        cities.add((City) element);
                    } else if (element instanceof Field) {
                        fields.add((Field) element);
                    } else if (element instanceof Road) {
                        roads.add((Road) element);
                    }
                }
            }

            // debug !
//            System.out.println("   " + cities.size() + " ville(s)");
//            for (City city : cities) {
//                System.out.println("        la ville " + city.hashCode() + " est composée de " + city.getTiles().size() + " tuile(s)" + city.getTiles() + "avec " + city.getPawns().size() + " pawns");
//
//                if (city.isComplete()) {
//                    System.out.println("                 elle est complète");
//                } else {
//                    System.out.println("                 et comporte " + city.openZones.size() + " ouvertures " + city.openZones);
//                }
//            }
//
//
//            System.out.println("   " + fields.size() + " champs");
//            for (Field field : fields) {
//                System.out.println("        le champ " + field.hashCode() + " est composée de " + field.getTiles().size() + " tuile(s)" + field.getTiles() + "avec " + field.getPawns().size() + " pawns");
//            }
//
//            System.out.println("   " + roads.size() + " routes");
//            for (Road road : roads) {
//                System.out.println("        la route " + road.hashCode() + " est composée de " + road.getTiles().size() + " tuile(s)" + road.getTiles() + "avec " + road.getPawns().size() + " pawns");
//                if (road.isComplete()) {
//                    System.out.println("                 elle est complète");
//                } else {
//                    System.out.println("                 et comporte " + road.openZones.size() + " ouvertures " + road.openZones);
//                }
//            }
//
//            System.out.println("");
        }

    }

    public void clearBoard() {

        tiles = new HashMap<Position, Tile>();
        holes = new ArrayList<Position>();

    }

    public void CanPlacePawn(Tile tile) {
    }

    public boolean CanPlaceTile(Tile tile, Position pos) {
        boolean result = true;

        if (tiles.get(pos) != null) {

            return false;

        }


        // check north Tile
        Tile northTile = tiles.get(new Position(pos.x, pos.y - 1));
        if (northTile != null) {

            if (northTile.getZone(Card.S).getType() != tile.getZone(Card.N).getType()) {
                return false;
            }
        }

        // check south Tile
        Tile southTile = tiles.get(new Position(pos.x, pos.y + 1));
        if (southTile != null) {

//            System.out.println("southTile (N) = " + southTile.getZone(Card.N).getType());
//            System.out.println("tile (S) = " + tile.getZone(Card.S).getType());


            if (southTile.getZone(Card.N).getType() != tile.getZone(Card.S).getType()) {
                return false;
            }
        }


        // check east Tile
        Tile eastTile = tiles.get(new Position(pos.x + 1, pos.y));
        if (eastTile != null) {

            if (eastTile.getZone(Card.W).getType() != tile.getZone(Card.E).getType()) {
                return false;
            }
        }

        // check west Tile
        Tile westTile = tiles.get(new Position(pos.x - 1, pos.y));
        if (westTile != null) {

            if (westTile.getZone(Card.E).getType() != tile.getZone(Card.W).getType()) {
                return false;
            }
        }

        return result;

    }

    public Map<Position, List<Integer>> findAvailablePosition(Tile tile2Place) {

        // first Tile
        if (holes.isEmpty()) {
            Map<Position, List<Integer>> positions = new HashMap();

            List<Integer> rotations = new ArrayList(4);
            for (int i = 0; i < 3; i++) {
                rotations.add(i);
            }
            positions.put(new Position(0, 0), rotations);

            return positions;

        }

        // Others Tiles
        Map<Position, List<Integer>> positions = new HashMap();

        // for each hole, check the needed rotation
        for (Position hole : holes) {

            List<Integer> rotations = new ArrayList();

            // check hole with no rotation
            tile2Place.rotate0();
            if (CanPlaceTile(tile2Place, hole)) {
                rotations.add(0);
            }

            // check hole with rotation 90
            tile2Place.rotate90();
            if (CanPlaceTile(tile2Place, hole)) {
                rotations.add(1);
            }

            // check hole with rotation 180
            tile2Place.rotate180();
            if (CanPlaceTile(tile2Place, hole)) {
                rotations.add(2);
            }

            // check hole with rotation 270
            tile2Place.rotate270();
            if (CanPlaceTile(tile2Place, hole)) {
                rotations.add(3);
            }

            // reboot rotation
            tile2Place.rotate0();

            if (!rotations.isEmpty()) {
                positions.put(hole, rotations);
            } else {
//                System.out.println("Je ne peux pas placer la tuile en (" + hole.x + "," + hole.y + ")");
            }

        }
        return positions;
    }

    private void updateBoardElements(Tile tile) {

//        tile.createElements();

//        System.out.println("##############");
//        System.out.println("je suis en train de placer la tuile " + tile.number);

//        stopRecur.clear();
//        System.out.println("mergeElements(" + tile.number + "," + Card.N + ")");
        mergeElements(tile, Card.N);

//        stopRecur.clear();
//        System.out.println("mergeElements(" + tile.number + "," + Card.S + ")");
        mergeElements(tile, Card.S);

//        stopRecur.clear();
//        System.out.println("mergeElements(" + tile.number + "," + Card.E + ")");
        mergeElements(tile, Card.E);

//        stopRecur.clear();
//        System.out.println("mergeElements(" + tile.number + "," + Card.W + ")");
        mergeElements(tile, Card.W);

    }

    private void mergeElements(Tile newTile, int card) {

//        System.out.println("Lancement de la récurssion sur la tuile " + newTile.number + " avec la cardinalité " + card);

//        stopRecur.add(newTile);

        Tile nearTile = null;

        if (card < 3) {
//            System.out.println("Tuile nord");
            nearTile = tiles.get(new Position(newTile.getPosition().x, newTile.getPosition().y - 1));
        } else if (card < 6) {
//            System.out.println("Tuile est");
            nearTile = tiles.get(new Position(newTile.getPosition().x + 1, newTile.getPosition().y));
        } else if (card < 9) {
//            System.out.println("Tuile sud");
            nearTile = tiles.get(new Position(newTile.getPosition().x, newTile.getPosition().y + 1));
        } else if (card < 12) {
//            System.out.println("Tuile west");
            nearTile = tiles.get(new Position(newTile.getPosition().x - 1, newTile.getPosition().y));
        }

        if (nearTile == null) {
//            System.out.println("pas de tuile");
            return;
        }

        Zone newZone = newTile.getZone(card);


        // propagate newElt INSIDE nearTile
        Element newElt = newZone.getElement();

        // get the reverse side of the (card) on the near tile
        Zone nearZone = nearTile.getReverseZone(card);

        Element nearElt = nearZone.getElement();

        // Stop if we tempt to merge a elt with himself
        if (newElt == nearElt) {
            return;
        }

        newElt.absorb(nearElt);
        nearZone.setElement(newElt);
        cleanOZs(newElt, newTile, nearTile, card);

//        nearZone.propagateElement();

        // propagate newElt in neighfouhood of nearTile -- Recusion
        for (Zone zone : nearZone.getLinks()) {
            mergeElements(nearTile, zone.card);
        }

        // Elt is a road, so we have to MERGE small fields around the road
        if (newZone.getType() == ZoneType.ROAD) {

//            System.out.println("****");
//
//            if (newTile.number == 21) {
////                return; 
//                System.out.println("");
//            }

//            leftzone of the road
//            Zone leftZone = newTile.getZone(newZone.card - 1);
            Zone leftZone = newTile.getZone(card - 1);
            Zone leftRZone = nearTile.getReverseZone(card - 1);
//            System.out.println("merge " + leftZone.getElement().hashCode() + " de la tuile " + newTile.number + " de card " + leftZone.card + " avec " + leftRZone.getElement().hashCode() + "  la tuile " + nearTile.number + " de card " + leftRZone.card);
            mergeSmallFields(leftZone, leftRZone);

            for (Zone zone : leftRZone.getLinks()) {
                mergeElements(nearTile, zone.card);
            }


//            rightzone of the road            
            Zone rightZone = newTile.getZone(card + 1);
            Zone rightRZone = nearTile.getReverseZone(card + 1);
//            System.out.println("merge " + rightZone.getElement().hashCode() + "de la tuile " + newTile.number + " de card " + rightZone.card + " avec " + rightRZone.getElement().hashCode() + " la tuile " + nearTile.number + " de card " + rightRZone.card);
            mergeSmallFields(rightZone, rightRZone);


//            rightzone of the road            
//            Zone rightZone = newTile.getZone(newZone.card + 1);
//            Zone rightRZone = nearTile.getReverseZone(rightZone.card);
            for (Zone zone : rightRZone.getLinks()) {
                mergeElements(nearTile, zone.card);
            }
//            System.out.println("****");

        }

//        System.out.println("Fin de la récursion sur la tuile " + newTile.number + " avec la cardinalité " + card);

    }

    private void mergeSmallFields(Zone newZone, Zone nearZone) {

        Element elt = newZone.getElement();
        Element nearElt = nearZone.getElement();

        elt.absorb(nearElt);
        nearZone.setElement(elt);
//        nearZone.propagateElement();


    }

    private void cleanOZs(Element newElt, Tile newTile, Tile nearTile, int card) {

        System.out.println("CleanOZ");

        Zone nearZone = nearTile.getReverseZone(card);

//        System.out.println("test si " + newElt.hashCode() + " contient l'OZ " + "[ " + nearTile.toString() + " , " + nearZone.card + " ]");

        if (newElt.containsOZ(nearTile, nearZone)) {

//            System.out.println("remove à " + newElt.hashCode() + " l'OZ " + "[ " + nearTile.toString() + " , " + nearZone.card + " ]");
            newElt.removeOpenZone(nearTile, nearZone);

//            System.out.println("remove à " + newElt.hashCode() + " l'OZ " + "[ " + newTile.toString() + " , " + newTile.getZone(card).card + " ]");
            newElt.removeOpenZone(newTile, newTile.getZone(card));


        } else {
//            System.out.println(newElt.hashCode() + " contient PAS l'OZ " + "[ " + nearTile.toString() + " , " + nearZone.card + " ]");
        }

//        System.out.println(newElt.openZones.size() + " ozs pour " + newElt.hashCode() + " :" + newElt.openZones);
    }
}
